# ActsAsHotRecord

# a simple plugin module that flags a record as hot
# and get the records base on its hotness
# @author: Marjun Pagalan
#
module June
  module Acts
    # ----------------------
    # acts_as_hot_record
    # flags the record as a hot_record for site_owners/site_admin to show on their home page
    #
    # only one record will be hot with a max_level of hotness
    # ----------------------
    #   example usage
    #
    #   class Book < ActiveRecord::Base
    #     acts_as_hot_record
    #   end
    #
    #   book = Book.find(:first)
    #   book.set_hotness(:level => 1 ) # this sets the hotness of book to the level given
    #
    #   book.set_hotness(:level => 1, expiration_date => Date.today + 1 ) #this sets the hotness of book to the level given and the expiration date of hotness
    #
    #   Book.hot_records #returns all hot records of the AR Class
    #   Book.hot_records( :level => 1..2  , :limit => 5) #returns all hot records of the AR Class
    #

    module HotRecord

      class HotnessLevelError < RuntimeError; end
      class HotnessMaxDuplicateError < RuntimeError; end

      DATE_NOW = Date.today #const for the current date

      def self.included(base) #:nodoc:
        base.extend(ClassMethods)
      end

      # This acts as allows a polymorphic link between arbitrary objects.
      module ClassMethods
      
	def acts_as_hot_record(options={})

          # This is more for the future if we want to have more functions
          include HotRecord::InstanceMethods
          extend  HotRecord::SingletonMethods

          # this allows you to redefine the acts' options for each subclass, however
          options[:min_level]         ||= 1
          options[:max_level]         ||= options[:min_level].to_i + 1
          options[:level]             ||= (options[:min_level]..options[:max_level])
          options[:force] = true if options[:force].nil?

          # reset the min_level with the given level range
          if options[:level].class == Range
            options[:min_level] = options[:level].first
            options[:max_level] = options[:level].last
          end

          #hot_record_options is available to processors and backends
          class_inheritable_accessor :hot_record_options
          self.hot_record_options = options
        end
      
      end

      # Class level methods
      module SingletonMethods
	
	# Generate the hot columns on a table, to be used when creating the table
        # in a migration. This is the preferred way to do in a migration that creates
        # new tables as it will make it as part of the table creation, and not generate
        def generate_hot_columns table
          table.column :hotness_level,      :integer
          table.column :hotness_expired_at, :datetime
	end
  
	# Create the needed columns for acts_as_hot_record.
        # To be used during migration, but can also be used in other places.
        def add_hot_columns
          if !self.content_columns.find { |c| 'hotness_level' == c.name }
            self.connection.add_column table_name, :hotness_level,      :integer
            self.connection.add_column table_name, :hotness_expired_at, :datetime
            self.reset_column_information
          end
        end

        # Remove the acts_as_hot_record specific columns added with add_hot_columns
        # To be used during migration, but can also be used in other places
        def remove_hot_columns
          if self.content_columns.find { |c| 'hotness_level' == c.name }
            self.connection.remove_column table_name, :hotness_level
            self.connection.remove_column table_name, :hotness_expired_at
            self.reset_column_information
          end
        end

        # return hot records
        # accepts :level => nil, :limit => nil , :order => "hotness_level DESC" || "hotness_level ASC"
        def hot_records(options={})
          options[:level]             ||= (hot_record_options[:min_level]..hot_record_options[:max_level])
          options[:limit]             ||=  10
          options[:order]             ||=  "hotness_level DESC"

          lvl = hot_record_options[:level].map{|i| i}
          level = options[:level]
          # does all necessary sanitazation of level options
          case level.class
            when Range
                    level = level.map{|i| i}
                    raise HotnessLevelError , "Level is not in range"  if level.map{ |o| lvl.include?(o) }
            when Integer
                    level = level.to_a
                    raise HotnessLevelError , "Level is not in range"  if level.map{ |o| lvl.include?(o) }
            when Array
                    level
                    raise HotnessLevelError , "Level is not in range"  if level.map{ |o| lvl.include?(o) }
          end

          date_now = HotRecord::DATE_NOW
          # finds our hot records based on the conditions we set
          hot_records = self.find( :all,
                                   :conditions => ['hotness_level IN (?) AND hotness_expired_at > ? ', level , date_now ],
                                   :limit => options[:limit],
                                   :order => options[:order] )
          hot_records
	end
  
      end

      # This are the methods that are instance level
      module InstanceMethods

        # setter for the hotness attribute of the object
        # only one object of the same class will have the max hotness level
        def set_hotness(options ={})
          date_now = HotRecord::DATE_NOW

          options[:level]             ||= hot_record_options[:min_level]
          options[:expiry_date]       ||= date_now + 1  #set our default expiry date ~TODO: could be configurable

          # we check if the level is the max_level for the hotness of the record
          # since we have a special handler for this kind of hotness_level
          if options[:level] == hot_record_options[:max_level]
            hot_record = self.class.find( :first ,
                             :conditions => [" hotness_level = ? AND id <> ? AND hotness_expired_at > ? ", options[:level] , self.id, date_now])
            if hot_record
              raise HotnessMaxDuplicateError , "Another record is on the max level of hotness" unless hot_record_options[:force]
              hot_record.hotness_level = hot_record_options[:min_level]
              hot_record.save
            end
          end
          self.hotness_level      = options[:level]
          self.hotness_expired_at = options[:expiry_date]
          self.save

        end

        # we just un_hot our hot record by setting the hot attributes to nil
        def un_hot!
          self.hotness_level      = nil
          self.hotness_expired_at = nil
        end

      end
    end
  end
end

ActiveRecord::Base.send :include, June::Acts::HotRecord